<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>我的技术与生活——趣味NodeJS之笔趣阁的小蜘蛛 | Hexo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="shortcut icon" href="../../../../imgs/shortcut-icon.ico" type="image/x-icon">
  <link rel="stylesheet" href="../../../../css/public.css" />
  <link rel="stylesheet" href="../../../../css/layout.css" />
  <link rel="stylesheet" href="../../../../css/iconfont.css" />
  <link rel="stylesheet" href="../../../../css/APlayer.min.css" />
  <script src="../../../../js/APlayer.min.js"></script>
  <script src="../../../../js/jquery.min.js"></script>
  <script src="../../../../js/jquery.pjax.min.js"></script>

  <script src='//unpkg.com/valine/dist/Valine.min.js'></script>
  <script>
    document.title = `我的技术与生活——趣味NodeJS之笔趣阁的小蜘蛛`
  </script>
<meta name="generator" content="Hexo 6.0.0"></head>

<style>
  .load {
    width: 100%;
    height: 100vh;
    background-color: rgb(37, 35, 40);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: relative;
    z-index: 9999;
  }
  .load-circle {
    width: 80px;
    height: 80px;
    border: 8px solid orange;
    border-bottom-color: transparent;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    animation: rotate 1s linear infinite;
    filter: drop-shadow(0 0 3px orange);
  }
  .load-circle-inner {
    width: 40px;
    height: 40px;
    border: 8px solid orange;
    border-top-color: transparent;
    border-radius: 50%;
    animation: rotate-reverse .5s linear infinite;
  }
  .load-text {
    margin-top: 20px;
    font-size: 24px;
    color: orange;
    display: flex;
  }
  .load-text span {
    margin: 0 5px;
    text-shadow: 5px 5px 5px orange;
    animation: move 1s linear infinite;
  }
  .load-text span:nth-child(1) {
    animation-delay: -0.6s;
  }
  .load-text span:nth-child(2) {
    animation-delay: -0.5s;
  }
  .load-text span:nth-child(3) {
    animation-delay: -0.4s;
  }
  .load-text span:nth-child(4) {
    animation-delay: -0.3s;
  }
  .load-text span:nth-child(5) {
    animation-delay: -0.2s;
  }
  .load-text span:nth-child(6) {
    animation-delay: -0.1s;
  }
  @keyframes rotate {
    0% { transform: rotate(0); }
    100% { transform: rotate(360deg); }
  }
  @keyframes rotate-reverse {
    0% { transform: rotate(0); }
    100% { transform: rotate(-360deg); }
  }
  @keyframes move {
    0% { transform: translateY(0%) rotate(0) scale(1); }
    20% { transform: translateY(20%) rotate(10deg) scale(1.2); }
    80% { transform: translateY(-10%) rotate(-20deg) scale(.8);}
    100% { transform: translateY(0) rotate(0) scale(1); }
  }

  .progress {
    position: fixed;
    left: 0; top: 0;
    width: 0;
    height: 3px;
    background-color: green;
    transition: all cubic-bezier(0.215, 0.610, 0.355, 1) .1s;
    z-index: 9999;
  }

  .to-up {
    animation: toUp .5s 1;
  }
  .gray {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100vh;
    z-index: 9999;
    display: none;
    pointer-events: none;
    background-color: #000;
    mix-blend-mode: color;
  }
  @keyframes toUp {
    from { transform: translateY(15px); opacity: 0; }
    to { transform: translateY(0) ; opacity: 1; }
  }
</style>
<body>
  <div id="load" class="load">
    <div class="load-circle">
      <div class="load-circle-inner"></div>
    </div>
    <p class="load-text">
      <span>L</span>
      <span>O</span>
      <span>A</span>
      <span>D</span>
      <span>I</span>
      <span>N</span>
      <span>G</span>
    </p>
  </div>
  <div id="container" class="container w-100 vh-100" style="display: none;">
    <header class="header">
  <div class="header-wrapper">
    <div class="header-left">
      <div class="header-search">
        <input id="search-input" type="text" class="header-search--input" placeholder="请输入要检索的文章标题" />
        <span id="search-btn" class="header-search--icon"><i class="iconfont icon-sousuo"></i></span>
      </div>
      <div id="search-layer" class="header-search--layer hidden">
        <p class="title">
          <span>以下是搜索内容：</span>
          <span id="close-layer-btn">关闭</span>
        </p>
        <ul>
        </ul>
      </div>
    </div>
    <div class="header-right">
      <ul class="header-menu">
        <li>
          <a href="https://aizener.github.io/">
            <i class="header-menu--icon iconfont icon-shouye"></i>
            <span class="header-menu--span">首页</span>
          </a>
        </li>
        <li>
          <a href="https://aizener.github.io/log">
            <i class="header-menu--icon iconfont icon-rizhi"></i>
            <span class="header-menu--span">日志</span>
          </a>
        </li>
        <li>
          <a href="https://aizener.github.io/link">
            <i class="header-menu--icon iconfont icon-youqinglianjie"></i>
            <span class="header-menu--span">友情链接</span>
          </a>
        </li>
        <li>
          <a href="https://aizener.github.io/about">
            <i class="header-menu--icon iconfont icon-guanyuwomen"></i>
            <span class="header-menu--span">关于我</span>
          </a>
        </li>
      </ul>
    </div>
  </div>
</header>

<script>
  const ipt = document.querySelector('#search-input')
  const btn = document.querySelector('#search-btn')
  const layer = document.querySelector('#search-layer')
  const posts = JSON.parse(`[{"title":"关于H5新特性拖放API讲解","path":"2022/03/24/关于H5新特性拖放API讲解/"},{"title":"分享基于H5实现扫码功能","cover":"https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9c64349191a94bf19bcaeb14a62d328c~tplv-k3u1fbpfcp-zoom-crop-mark:3024:3024:3024:1702.awebp?","path":"2022/08/14/分享基于H5实现扫码功能/"},{"title":"在Vue项目中使用ESLint来检查代码","cover":"https://s3.bmp.ovh/imgs/2022/04/01/647275ce2adecce7.png","path":"2022/03/02/在Vue项目中使用ESLint来检查代码/"},{"title":"如何使用hexo-theme-cola主题","path":"2022/02/17/如何使用hexo-theme-cola主题/"},{"title":"基于Vue对主题修改的思考与实现","cover":"https://s3.bmp.ovh/imgs/2022/04/01/7d831a8e4b739ac2.png","path":"2022/04/01/基于Vue对主题修改的思考与实现/"},{"title":"趣味NodeJS之笔趣阁的小蜘蛛","cover":"https://s3.bmp.ovh/imgs/2022/04/01/05822287376d328d.png","path":"2022/03/08/趣味NodeJS之笔趣阁的小蜘蛛/"}]`)
  ipt.addEventListener('keyup', e => {
    if (e.key === 'Enter') {
      handleSearch()
    }
  })
  btn.addEventListener('click', () => {
    handleSearch()
  })

  document.querySelector('#close-layer-btn').addEventListener('click', () => {
    layer.classList.toggle('hidden')
  })

  function handleSearch() {
    if (ipt.value.trim() === '') {
      return
    }
    let html = ''
    const targetPosts = posts.filter(post => post.title.includes(ipt.value))
    targetPosts.forEach(post => {
      html += `
        <li>
          <div>
            <a href="/${post.path}">${post.title.replace(new RegExp(ipt.value), `<span>${ipt.value}</span>`)}</a>
          </div>
          <img src="${post.cover || '/imgs/default-cover.webp' }" />
        </li>
      `
    })
    if (html.trim () === '') {
      html += '<p class="empty">没有搜索到内容</p>'
    }
    layer.querySelector('ul').innerHTML = html
    layer.classList.remove('hidden')
  }
</script> 
    <section id="main" class="main">
      <div class="main-left-wrapper">
<div class="main-left">
  <div class="main-left--block">
    <div class="main-left--info">
      <img src="../../../../imgs/avatar.jpg"" class="main-left--avatar" />
      <div class="main-left--intro">
        <p class="main-left--name">Cola.</p>
        <div class="main-left--tags">
          <span class="main-left--tag">中二</span>
          <span class="main-left--tag">宅</span>
        </div>
      </div>
    </div>
  
    <div>
      <div class="main-left--motto">
        <p>“花有重开日，人无再少年”</p>
        <p>“一个简单普通的男孩”</p>
      </div>
      <div class="main-left--github">
        <span class="line"></span>
        <a target="_blank" rel="noopener" href="https://github.com/Aizener"><i class="logo iconfont icon-github-fill"></i></a>
        <span class="line"></span>
      </div>
      <div class="main-left--statics">
        <a href="/categories">
          <div>
            <span>2</span>
            <span>分类</span>
          </div>
        </a>
        <a href="/tags">
          <div>
            <span>7</span>
            <span>标签</span>
          </div>
        </a>
        <a href="/archives">
          <div>
            <span>4 </span>
            <span>归档</span>
          </div>
        </a>
      </div>
    </div>
  </div>

  <div class="main-left--block">
    <ul class="main-left--menu">
      
        <li>
          <a href="/">
            <span class="header-menu--span">小站首页</span>
            <i class="header-menu--icon iconfont icon-shouye"></i>
          </a>
        </li>
      
        <li>
          <a href="/log">
            <span class="header-menu--span">个人日志</span>
            <i class="header-menu--icon iconfont icon-rizhi"></i>
          </a>
        </li>
      
        <li>
          <a href="/link">
            <span class="header-menu--span">友情链接</span>
            <i class="header-menu--icon iconfont icon-youqinglianjie"></i>
          </a>
        </li>
      
        <li>
          <a href="/about">
            <span class="header-menu--span">关于自己</span>
            <i class="header-menu--icon iconfont icon-guanyuwomen"></i>
          </a>
        </li>
      
        <li>
          <a href="/tools">
            <span class="header-menu--span">我的工具</span>
            <i class="header-menu--icon iconfont icon-gongju"></i>
          </a>
        </li>
      
    </ul>
  </div>

  <div class="main-left--block">
    <div class="main-left--site">
      <h5 class="main-left--title">
        <span>站点信息</span>
        <i class="iconfont icon-zhandian"></i>
      </h5>
      <p class="main-left--subtitle">
        <span>文章数目：</span>
        <span>6 篇</span>
      </p>
      <p class="main-left--subtitle">
        <span>最近动态：</span>
        <span>6月前</span>
      </p>
      <p class="main-left--subtitle">
        <span>上线时间：</span>
        <span>410天</span>
      </p>
      <p class="main-left--subtitle">
        <span>当前版本：</span>
        <span>v1.0.2</span>
      </p>
    </div>
  </div>
</div></div>
      <div id="main-container" class="main-container">


  <style>
pre::-webkit-scrollbar {
  width: 5px;
  height: 10px;
  background-color:#F5F5F5;
}
/*定义滚动条轨道
内阴影+圆角*/
pre::-webkit-scrollbar-track {
  background-color:#F5F5F5;
}
/*定义滑块
内阴影+圆角*/
pre::-webkit-scrollbar-thumb {
  background-color: rgb(69, 83, 100);
}

pre:active {
  background-color: rgb(81, 95, 116);
}
</style>

<div class="article-container">
  <div class="article">
    <h1 class="article-title">趣味NodeJS之笔趣阁的小蜘蛛</h1>
    <div class="article-info">
      <div class="article-info--item">
        <div class="article-info--info">
          
          <div class="article-info--categories">
            <span>分类：</span>
            <a class="category-link" href="../../../../categories/NodeJS/">NodeJS</a>
          </div>
          
          
          <div class="article-info--tags">
            <span>标签：</span>
            <a class="tag-link" href="../../../../tags/JavaScript/" rel="tag">JavaScript</a><a class="tag-link" href="../../../../tags/%E7%88%AC%E8%99%AB/" rel="tag">爬虫</a>
          </div>
          
          <p class="article-info--date">日期：2022-03-08 21:18:20</p>
        </div>
        <img src="https://s3.bmp.ovh/imgs/2022/04/01/05822287376d328d.png" alt="" class="article-cover">
      </div>
    </div>
    <article class="article-content">
      <blockquote>
<p>作为一个可以让JavaScript运行在服务端的平台，近年来NodeJS不可谓不火，于是乎前端崽们多少都会一些。今儿，我就在学习之中分享一个NodeJS写的抓取笔趣阁小说的爬虫。</p>
</blockquote>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>因为个人比较喜欢看小说，所以笔趣阁就成了我看小说的一个重要途径。虽然笔趣阁是免费的一个平台，但是并没有提供下载小说的方式。<br>所以我就在想是否可以通过我所学的<code>NodeJS</code>知识来爬取上面的小说，所以就有下面的内容了。</p>
<h3 id="相关技术"><a href="#相关技术" class="headerlink" title="相关技术"></a>相关技术</h3><p>除了需要掌握<code>NodeJS</code>以外，还需要借助第三方库来帮助编码，毕竟站在巨人上才能看的更远。这里，我选择的是 <a target="_blank" rel="noopener" href="http://www.axios-js.com/">axios</a> 和 <a target="_blank" rel="noopener" href="https://cheerio.js.org/">cheerio</a> 两个库。</p>
<ul>
<li><code>aixos</code>  : 这个库我就不必介绍了，写前端的都懂；</li>
<li><code>cheerio</code> : 这个库简单一句话介绍就是服务端的<code>jQuery</code>。</li>
</ul>
<blockquote>
<p>为什么没有用比较新的 <a target="_blank" rel="noopener" href="https://pptr.dev/">puppeteer</a> 库？<br>这个库网上都比较推荐，能比较方便的爬取异步渲染的数据。但我使用<code>puppeteer</code>开发爬虫发现一个问题：当请求的页面过多时，占用的内存会变很多。即使一个简单的小说网站也有上千个请求，所以需要一定的优化方式，否则爬取速度会很缓慢。</p>
</blockquote>
<h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>笔趣阁作为一个服务端渲染的网站，所以使用<code>axios</code>+<code>cheerio</code>也能非常简单的爬取了。  </p>
<ol>
<li>找到目标小说的<code>url</code>，解析该小说的书名和目录（目录有对应章节的链接）；</li>
<li>遍历小说目录，通过请求对应的<code>path</code>，来解析出文章内容；</li>
<li>把解析的内容通过<code>fs</code>模块写到文件里。</li>
</ol>
<p>有了一个简单的思路，就可以开始编码实现了。</p>
<h3 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h3><ol>
<li><p>创建一个<code>index.js</code>文件，并通过<code>npm init -y</code>初始化，然后下载我们要用的依赖：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> --save axios cheerio<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>请求目标小说，获取内容：</p>
<pre class="line-numbers language-js" data-language="js"><code class="language-js"><span class="token keyword">const</span> Axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> cheerio <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'cheerio'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> baseURL <span class="token operator">=</span> <span class="token string">'https://www.xbiquge.la/'</span> <span class="token comment">// 笔趣阁的网站</span>
<span class="token keyword">const</span> outDir <span class="token operator">=</span> <span class="token string">'dist'</span> <span class="token comment">// 写入的文件目录</span>
<span class="token keyword">const</span> targetURL <span class="token operator">=</span> <span class="token string">'7/7877/'</span> <span class="token comment">// 通过网站url的规律，找到对应的书名pathname</span>
<span class="token keyword">const</span> axios <span class="token operator">=</span> Axios<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
  <span class="token literal-property property">timeout</span><span class="token operator">:</span> <span class="token number">0</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>baseURL <span class="token operator">+</span> targetURL<span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li>
</ol>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd03c76a4d2043b6940f6f5771ef0e36~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>从上图，可以看到，通过<code>axios</code>直接请求地址后，会获得返回的<code>HTML</code>内容，有了这个内容后，就可以通过<code>cheerio</code>来获取任意的内容。</p>
<ol start="3">
<li>分析节点信息，使用<code>cheerio</code>获取需要的信息：</li>
</ol>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac73254ef66a4acd8f6ea84798231c5e~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>找到有用的节点信息后，开始编码：</p>
<pre class="line-numbers language-js" data-language="js"><code class="language-js"><span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>baseURL <span class="token operator">+</span> targetURL<span class="token punctuation">)</span>
<span class="token keyword">const</span> $ <span class="token operator">=</span> cheerio<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">)</span>
<span class="token keyword">const</span> title <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#info h1'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> list <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#list a'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">each</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">idx<span class="token punctuation">,</span> item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
  list<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
    <span class="token literal-property property">index</span><span class="token operator">:</span> idx<span class="token punctuation">,</span>
    <span class="token literal-property property">url</span><span class="token operator">:</span> item<span class="token punctuation">.</span>attribs<span class="token punctuation">.</span>href<span class="token punctuation">,</span>
    <span class="token literal-property property">title</span><span class="token operator">:</span> item<span class="token punctuation">.</span>children<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>data
  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'书籍名称：'</span><span class="token punctuation">,</span> title<span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'书籍目录：'</span><span class="token punctuation">,</span> list<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>



<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7aa53df557b845339b69ca7578bf59db~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>从上面可以看到<code>cheerio</code>的<code>API</code>和<code>jQuery</code>基本一样方便，因此也很容易的获取到了目录的信息。有了目录的章节信息后，就可以依葫芦画瓢，遍历请求抓取对应内容了。<br>现在，能抓取到内容后，就可以考虑下一步：把抓取的文章内容保存到本。</p>
<ol start="4">
<li><p>保存文章内容到本地文件：<br>在<code>NodeJS</code>中，可以通过 <a target="_blank" rel="noopener" href="http://nodejs.cn/api/fs.html">fs</a> 模块来读写文件，使用起来非常的容易。</p>
<pre class="line-numbers language-js" data-language="js"><code class="language-js"><span class="token keyword">const</span> ws <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token function">resolve</span><span class="token punctuation">(</span>outDir<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>title<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.txt</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
  <span class="token literal-property property">flags</span><span class="token operator">:</span> <span class="token string">'a'</span> <span class="token comment">// 表示写入方式为追加</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> b <span class="token keyword">of</span> list<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  <span class="token keyword">const</span> content <span class="token operator">=</span> <span class="token string">'...'</span>  <span class="token comment">// 通过cheerio分别获取内容</span>
  ws<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>title <span class="token operator">+</span> <span class="token string">'\n'</span><span class="token punctuation">)</span>  <span class="token comment">// \n换行符</span>
  ws<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>content <span class="token operator">+</span> <span class="token string">'\n'</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>
ws<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li>
</ol>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73f1bc017972417cb7d7e3c1eed62cdb~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>正常情况下，就可以看到<code>dist</code>目录下生成的对应书名的<code>.txt</code>文件了，这样小说就算简单的爬取下来了。</p>
<h3 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h3><p>虽然实现了下载小说的目的，但发现了有几个比较严重的问题所在：</p>
<ol>
<li>小说章节太多，下载缓慢；</li>
<li>请求过于频繁，有些请求会失败，造成内容补全；</li>
<li>长时间的程序执行，没有反馈交互，显得程序像卡死。</li>
</ol>
<p>发现问题所在后，就得想办法来解决，思考之后想出以下方式：</p>
<ol>
<li>通过<code>setTimeout</code>来开启异步，加快下载速率；</li>
<li>使用<code>try/catch</code>配合递归的方式，一直等请求成功（可以设置一个重试次数）；</li>
<li>使用<code>cli-progress</code>来反馈下载进度。</li>
</ol>
<h4 id="分片下载"><a href="#分片下载" class="headerlink" title="分片下载"></a>分片下载</h4><p>通过<code>setTimeout</code>来异步下载小说，需要考虑顺序问题。这里定义一个<code>step</code>来表示分成几段来下载，最后来把片段给接上：</p>
<pre class="line-numbers language-js" data-language="js"><code class="language-js"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">downloadBook</span> <span class="token punctuation">(</span><span class="token parameter">title<span class="token punctuation">,</span> book<span class="token punctuation">,</span> p <span class="token operator">=</span> <span class="token number">5</span></span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  <span class="token keyword">let</span> c <span class="token operator">=</span> <span class="token number">0</span>  <span class="token comment">// 下载完成的片段数量</span>
  <span class="token keyword">const</span> d <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
    <span class="token literal-property property">book</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">ws</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isExist</span><span class="token punctuation">(</span>outDir<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    fs<span class="token punctuation">.</span><span class="token function">mkdirSync</span><span class="token punctuation">(</span>outDir<span class="token punctuation">)</span>
  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
    fs<span class="token punctuation">.</span><span class="token function">readdirSync</span><span class="token punctuation">(</span>outDir<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">file</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fs<span class="token punctuation">.</span><span class="token function">statSync</span><span class="token punctuation">(</span><span class="token function">pathFor</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isDirectory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        fs<span class="token punctuation">.</span><span class="token function">unlinkSync</span><span class="token punctuation">(</span><span class="token function">pathFor</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  <span class="token punctuation">&#125;</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator">&lt;</span> p <span class="token punctuation">;</span> i <span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> step <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>book<span class="token punctuation">.</span>length <span class="token operator">/</span> p<span class="token punctuation">)</span>
    d<span class="token punctuation">.</span>book<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>i <span class="token operator">===</span> p <span class="token operator">-</span> <span class="token number">1</span> <span class="token operator">?</span> book<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>i <span class="token operator">*</span> step<span class="token punctuation">)</span> <span class="token operator">:</span> book<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>i <span class="token operator">*</span> step<span class="token punctuation">,</span> <span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">*</span> step<span class="token punctuation">)</span><span class="token punctuation">)</span>
    d<span class="token punctuation">.</span>ws<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token function">pathFor</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>title <span class="token operator">+</span> i<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.txt</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
      <span class="token literal-property property">flags</span><span class="token operator">:</span> <span class="token string">'a'</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    d<span class="token punctuation">.</span>ws<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'finish'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
      c <span class="token operator">++</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">===</span> p<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'\n下载完成，文件归档中...'</span><span class="token punctuation">)</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator">&lt;</span> p <span class="token punctuation">;</span> i <span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
          <span class="token keyword">const</span> target <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">createWriteStream</span><span class="token punctuation">(</span><span class="token function">pathFor</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>title<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.txt</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
            <span class="token literal-property property">flags</span><span class="token operator">:</span> <span class="token string">'a'</span>
          <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
          <span class="token keyword">const</span> rs <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">createReadStream</span><span class="token punctuation">(</span><span class="token function">pathFor</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>title <span class="token operator">+</span> i<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.txt</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span>
          <span class="token keyword">await</span> <span class="token function">pipeSync</span><span class="token punctuation">(</span>rs<span class="token punctuation">,</span> target<span class="token punctuation">)</span>
        <span class="token punctuation">&#125;</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'下载完成！'</span><span class="token punctuation">)</span>
        process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  <span class="token punctuation">&#125;</span>

  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">开始下载</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>title<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">了：</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>

  <span class="token keyword">const</span> bar <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">cliProgress<span class="token punctuation">.</span>SingleBar</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
    <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">'下载进度：[&#123;bar&#125;] &#123;percentage&#125;% | 预估时间: &#123;eta&#125;s | &#123;value&#125;/&#123;total&#125;'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">barIncompleteChar</span><span class="token operator">:</span> <span class="token string">'-'</span>
  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> cliProgress<span class="token punctuation">.</span>Presets<span class="token punctuation">.</span>rect<span class="token punctuation">)</span>
  bar<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span>book<span class="token punctuation">.</span>length<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator">&lt;</span> p <span class="token punctuation">;</span> i <span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>
      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> b <span class="token keyword">of</span> d<span class="token punctuation">.</span>book<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">const</span> <span class="token punctuation">&#123;</span> $c <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getHTMLContent</span><span class="token punctuation">(</span>baseURL <span class="token operator">+</span> b<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
          <span class="token literal-property property">$c</span><span class="token operator">:</span> <span class="token string">'#content'</span>
        <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> b<span class="token punctuation">.</span>title<span class="token punctuation">)</span>
        <span class="token keyword">const</span> content <span class="token operator">=</span> $c<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        d<span class="token punctuation">.</span>ws<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>b<span class="token punctuation">.</span>title <span class="token operator">+</span> <span class="token string">'\n'</span><span class="token punctuation">)</span>
        d<span class="token punctuation">.</span>ws<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>content <span class="token operator">+</span> <span class="token string">'\n'</span><span class="token punctuation">)</span>
        bar<span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">&#125;</span>
      d<span class="token punctuation">.</span>ws<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
  <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>以上就是分段来异步下载的一个简单实现，接着看怎么处理异常：</p>
<h4 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h4><p>请求的次数多了后，肯定会出现丢包的现象，从而导致内容不全，这种时候就需要做对应处理了：</p>
<pre class="line-numbers language-js" data-language="js"><code class="language-js"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getHTMLContent</span> <span class="token punctuation">(</span>url<span class="token punctuation">,</span> selectors<span class="token punctuation">,</span> title <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
  <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span>
    <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span>
    <span class="token keyword">const</span> $ <span class="token operator">=</span> cheerio<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">)</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> k <span class="token keyword">in</span> selectors<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
      result<span class="token punctuation">[</span>k<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">$</span><span class="token punctuation">(</span>selectors<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">&#125;</span>
    <span class="token keyword">return</span> result
  <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'\n下载'</span> <span class="token operator">+</span> <span class="token punctuation">(</span>title <span class="token operator">||</span> url<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'出现网络错误，将重新尝试...'</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token function">getHTMLContent</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> selectors<span class="token punctuation">)</span>
  <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>当因为网络失败后，会<code>try</code>捕获走<code>catch</code>方法，在<code>catch</code>方法里我们递归调用，从新再次请求这次失败的章节，保证内容不会丢失。</p>
<h3 id="进度条反馈"><a href="#进度条反馈" class="headerlink" title="进度条反馈"></a>进度条反馈</h3><p>使用进度条的话，需要下载 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/cli-progress">cli-progress</a> 库：</p>
<pre class="line-numbers language-shell" data-language="shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> --save cli-progress<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>使用方式非常的简单：</p>
<pre class="line-numbers language-js" data-language="js"><code class="language-js"><span class="token keyword">const</span> bar <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">cliProgress<span class="token punctuation">.</span>SingleBar</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>
    <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">'下载进度：[&#123;bar&#125;] &#123;percentage&#125;% | 预估时间: &#123;eta&#125;s | &#123;value&#125;/&#123;total&#125;'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">barIncompleteChar</span><span class="token operator">:</span> <span class="token string">'-'</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">,</span> cliProgress<span class="token punctuation">.</span>Presets<span class="token punctuation">.</span>rect<span class="token punctuation">)</span>
bar<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// 初始化总数，当前数</span>
bar<span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// +1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>其中的几个参数表达意思为：</p>
<ul>
<li><code>format</code> : 表示格式化后的显示内容；</li>
<li><code>barIncompleteChar</code> : 未下载的显示字符；</li>
<li><code>start</code> : 初始化进度条，第一个参数是总是，第二个是完成数：一般为0；</li>
<li><code>increment</code> : 完成数自增1.</li>
</ul>
<h3 id="完成"><a href="#完成" class="headerlink" title="完成"></a>完成</h3><p>优化完成后，就可以看看现在的效果了：</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f236f3433e84a1fa6d78e22720b24b7~tplv-k3u1fbpfcp-watermark.image" alt="niceai.gif"></p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39b6c9d3355e43eda542e0af166c1c1c~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p>
<p>当下载完成之后，就会把这几个文件归档为一个<code>斗破苍穹.txt</code>的文件。</p>
<p><strong>以上，就是怎样使用NodeJS抓取一个普通网站内容的思路了，谢谢阅读Thanks♪(･ω･)ﾉ。</strong></p>

    </article>
    
    <div class="read-nums">
      <!-- id 将作为查询条件 -->
      <span id="2022/03/08/趣味NodeJS之笔趣阁的小蜘蛛/" class="leancloud_visitors" data-flag-title="Your Article Title">
        <em class="post-meta-item-text">浏览量</em>
        <i class="leancloud-visitors-count"></i>
      </span>
    </div>
    <div class="comments-intro">
      <h2>评论区</h2>
      <p>欢迎你留下宝贵的意见，昵称输入QQ号会显示QQ头像哦~</p>
    </div>
    <div id="vcomments" class="vcomments"></div>
    
  </div>
  <div class="article-catelogue">
    <div class="article-catelogue--wrapper">
      <div class="catelogue catelogue-1">
        <h3>目录</h3>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">前言</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9B%B8%E5%85%B3%E6%8A%80%E6%9C%AF"><span class="toc-number">1.1.</span> <span class="toc-text">相关技术</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%80%9D%E8%B7%AF"><span class="toc-number">1.2.</span> <span class="toc-text">思路</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%9E%E7%8E%B0"><span class="toc-number">1.3.</span> <span class="toc-text">实现</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BC%98%E5%8C%96"><span class="toc-number">1.4.</span> <span class="toc-text">优化</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%88%86%E7%89%87%E4%B8%8B%E8%BD%BD"><span class="toc-number">1.4.1.</span> <span class="toc-text">分片下载</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86"><span class="toc-number">1.4.2.</span> <span class="toc-text">异常处理</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BF%9B%E5%BA%A6%E6%9D%A1%E5%8F%8D%E9%A6%88"><span class="toc-number">1.5.</span> <span class="toc-text">进度条反馈</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AE%8C%E6%88%90"><span class="toc-number">1.6.</span> <span class="toc-text">完成</span></a></li></ol></li></ol>
      </div>
      
        <div class="catelogue catelogue-2">
          
            <p>
              <span>上一篇：</span>
              <a href="/2022/03/24/关于H5新特性拖放API讲解/">关于H5新特性拖放API讲解</a>
            </p>
           
          
            <p>
              <span>下一篇</span>
              <a href="/2022/03/02/在Vue项目中使用ESLint来检查代码/">在Vue项目中使用ESLint来检查代码</a>
            </p>
          
        </div>
      
    </div>
  </div>
</div>

<script src="http://cdn.yangxiang.cc/prism.js"></script>

<script>
  // var定义，避免pjax重新进来造成的重复声明错误
  var config = JSON.parse('{"enable":true,"appId":"Pf8zCXGEH1qsprnWfikVVujL-gzGzoHsz","appKey":"qOqoiUHhH1TGtLRUYURkLRQX","placeholder":"请留下你宝贵的意见吧~","meta":["nick"],"recordIP":true,"visitor":true,"enableQQ":true}')
  new Valine({
    el: '#vcomments',
    appId: config.appId,
    appKey: config.appKey,
    placeholder: config.placeholder,
    meta: config.meta,
    recordIP: config.recordIP,
    visitor: config.visitor,
    enableQQ: config.enableQQ,
    path: '2022/03/08/趣味NodeJS之笔趣阁的小蜘蛛/'
  })
</script>


<script>
  $(document).on('pjax:complete', function() {
    const tocs = document.querySelector('.toc')
    const links = tocs ? tocs.querySelectorAll('a') : []
    links.forEach(link => {
      link.addEventListener('click', e => {
        const href = decodeURIComponent(a.href)
        href.search(/#(.*)/)
        const id = RegExp.$1
        const target = document.querySelector('#' + id)
        const top = target.offsetTop
        document.documentElement.scrollTo({
          top: top - 100,
          behavior: 'smooth'
        })
        e.preventDefault()
      })
    })
  })
</script> 

</div>
      <div class="main-right-wrapper"><div class="main-right">
  <div class="main-right--board">
    <div class="main-right--title">
      <h5>公告栏</h5>
      <i class="iconfont icon-gonggao"></i>
    </div>
    <div class="main-right--content">
      Hello~大噶好。唔系可乐爱宅着，欢迎你们来到我的博客小站，希望能在这里收获到有用的东西哦！ 
    </div>
  </div>

  <div id="aplayer" class="main-right--music"></div>

  <div class="operate-items">
    <div class="operate-item backtop">
      <i class="iconfont icon-huidaodingbu"></i>
      <span>回到顶部</span>
    </div>
    
    <div class="operate-item turn-comment hidden">
      <i class="iconfont icon-pinglun"></i>
      <span>查看评论</span>
    </div>
    
  </div>

  <div class="main-right--site">
    <div class="main-right--power">
      <p>Power By <a target="_blank" rel="noopener" href="https://hexo.io/zh-cn/docs/">Hexo</a>.</p>
      <p>Theme：<a target="_blank" rel="noopener" href="https://github.com/Aizener/hexo-theme-cola">Cola.</a></p>
    </div>
    <p class="main-right--refer"><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn/#/Integrated/index">蜀ICP备2022005384号-1</a> </p>
  </div>
</div>

<script>
  function setOperateItem () {
    const reg = /\d{4}\/\d{2}\/\d{2}\/.+/
    const path = location.pathname
    const commentDom = document.querySelector('.turn-comment')
    if (commentDom) {
      if (reg.test(path) || path.match(/\/log\/.+/)) {
        commentDom.classList.remove('hidden')
      } else {
        commentDom.classList.add('hidden')
      }
    }
  }

  setOperateItem()
  const musics = JSON.parse(`[{"name":"安河桥","artist":"宋冬野","url":"http://ting6.yymp3.net:82/new25/songdongye/11.mp3","cover":"https://img2.baidu.com/it/u=1260056724,1076343118&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"},{"name":"卡布达","artist":"暂无","url":"music/kabuda.mp3","cover":"https://img2.baidu.com/it/u=705831265,2862720033&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"}]`)
  const ap = new APlayer({
    container: document.querySelector('#aplayer'),
    audio: musics,
  })

  $(document).on('pjax:complete', function() {
    setOperateItem()
  })

  document.querySelector('.backtop').addEventListener('click', () => {
    document.documentElement.scrollTo({
      top: 0,
      behavior: 'smooth'
    })
  })
  const dom = document.querySelector('.turn-comment')
  dom && dom.addEventListener('click', () => {
    const commentDom = document.querySelector('.comments-intro')
    if (!commentDom) return
    const top = commentDom.offsetTop, height = commentDom.offsetHeight
    document.documentElement.scrollTo({
      top: top - 2 * height,
      behavior: 'smooth'
    })
  })
</script></div>
    </section>
  </div>
  <div id="progress" class="progress"></div>
  <div id="gray" class="gray"></div>

  <script>
    function initScroll () {
      document.addEventListener('scroll', () => {
        const doc = document.documentElement
        const scrollTop = doc.scrollTop
        const pageHeight = doc.offsetHeight
        const clientHeight = doc.clientHeight
        const ratio = scrollTop / (pageHeight - clientHeight)
        const progress = document.querySelector('#progress')
        const avatarImg = document.querySelector('.main-left--avatar')
        progress.style.width = (100 * ratio) + '%'
        avatarImg.style.transform = `rotate(${360 * ratio}deg)`
      })
    }

    const rootPath = "/"

    const checkAndSetArticlePageLayout = () => {
      if (/^\/?\d{4}\/\d{2}\/\d{2}\/.*/.test(location.pathname.replace(rootPath, ''))) {
        $('.main-container, .main-right, .main-right-wrapper').addClass('is-article')
      } else {
        $('.main-container, .main-right, .main-right-wrapper').removeClass('is-article')
      }
    }

    const gray = "none"
    const setGrayStyle = () => {
      if (gray === 'none') {
        return
      } else if (gray === 'index') {
        location.pathname === '/' ? $('#gray').show() : $('#gray').hide()
      } else if (gray === 'all') {
        $('#gray').show()
      }
    }
    setGrayStyle()


    window.onload = function () {
      checkAndSetArticlePageLayout()
      setTimeout(() => {
        $('#load').slideUp()
        $('#container').slideToggle()
        setTimeout(() => {
          initScroll()
        }, 500)
      }, 500)
    }
    
    let status = 0
    // 对所有链接跳转事件绑定pjax容器container
    $(document).pjax('a[target!=_blank]', '#main-container', {
      container: '#main-container',
      fragment: '#main-container',
      timeout: 8000
    })

    $(document).on('pjax:start', function() {
    })
    $(document).on('pjax:complete', function() {
      status = 0
      $('.main-container').addClass('to-up').on('animationend', function() {
        $(this).removeClass('to-up')
      })
      setGrayStyle()
      checkAndSetArticlePageLayout()
    })
    $(document).on('pjax:popstate', function() {
      status = -1
      checkAndSetArticlePageLayout()
      // $('.main-container').fadeIn()
    })
  </script>
</body>
</html>